﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FlitBit.Core;
using FlitBit.Core.Parallel;

namespace FlitBit.Cache
{
	/// <summary>
	///   Abstract base class for caches backed by remote services.
	/// </summary>
	/// <typeparam name="TKey">key type K</typeparam>
	/// <typeparam name="TValue">value type V</typeparam>
	/// <typeparam name="TRep">representation type R</typeparam>
	public abstract class RemoteCache<TKey, TValue, TRep> : Disposable, ICache<TKey, TValue>
	{
		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="name">the cache's name</param>
		protected RemoteCache(string name)
		{
			Contract.Requires<ArgumentNullException>(name != null);
			Contract.Requires<ArgumentException>(name.Length > 0);

			this.Name = name;
		}

		/// <summary>
		///   Performs a singular get against the remote caching service. Implementations
		///   must call the continuation with results.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">a continuation called upon completion</param>
		protected abstract void RemoteGet(TKey key, Continuation<Tuple<TKey, TRep>> continuation);

		/// <summary>
		///   Performs a singular put against the remote caching service. If a continuation
		///   is given, implementations must call the continuation with results.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="item">the item</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		protected abstract void RemotePut(TKey key, TRep item, Continuation continuation);

		/// <summary>
		///   Performs a singular remove against the remote caching service. If a continuation
		///   is given, implementations must call the continuation with results.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		protected abstract void RemoteRemove(TKey key, Continuation<Tuple<TKey, TRep>> continuation);

		/// <summary>
		///   Restores an item from a representation
		/// </summary>
		/// <param name="representation">the representation</param>
		/// <returns>the restored item</returns>
		protected abstract TValue RestoreItem(TRep representation);

		/// <summary>
		///   Produces representation type R from an item.
		/// </summary>
		/// <param name="item">the item</param>
		/// <returns>a representation of the item</returns>
		protected abstract TRep TransformItem(TValue item);

		/// <summary>
		///   Performs a mulitple put against the remote caching service. The base implementation
		///   performs a series of singular puts and accumulates the results.
		/// </summary>
		/// <param name="items">the items</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		protected virtual void RemotePut(IEnumerable<Tuple<TKey, TRep>> items, Continuation continuation)
		{
			Contract.Requires<ArgumentNullException>(items != null);

			var ex = default(Exception);
			var ii = items as Tuple<TKey, TRep>[] ?? items.ToArray();
			var callbacks = ii.Count();
			Continuation singular = e =>
			{
				var final = Interlocked.Decrement(ref callbacks) == 0;
				if (Util.VolatileRead(ref ex) == null)
				{
					if (e != null)
					{
						Interlocked.CompareExchange(ref ex, e, null);
					}
				}
				if (final)
				{
					continuation(Util.VolatileRead(ref ex) != null ? ex : null);
				}
			};
			try
			{
				foreach (var item in ii)
				{
					RemotePut(item.Item1, item.Item2, singular);
				}
			}
			catch (Exception e)
			{
				continuation(e);
			}
		}

		/// <summary>
		///   Performs mulitple removes against the remote caching service. The base implementation
		///   performs a series of singular removes and accumulates the results.
		/// </summary>
		/// <param name="keys">the items' keys</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		protected virtual void RemoteRemove(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TRep>>> continuation)
		{
			Contract.Requires<ArgumentNullException>(keys != null);

			var accum = new ConcurrentBag<Tuple<TKey, TRep>>();
			var ex = default(Exception);
			var kk = keys as TKey[] ?? keys.ToArray();
			var callbacks = kk.Count();
			Continuation<Tuple<TKey, TRep>> singular = (e, res) =>
			{
				var final = Interlocked.Decrement(ref callbacks) == 0;
				if (Util.VolatileRead(ref ex) == null)
				{
					if (e != null)
					{
						Interlocked.CompareExchange(ref ex, e, null);
					}
					else if (res != null)
					{
						accum.Add(res);
					}
				}
				if (final)
				{
					if (Util.VolatileRead(ref ex) != null)
					{
						continuation(ex, null);
					}
					else
					{
						continuation(null, accum);
					}
				}
			};
			try
			{
				foreach (var k in kk)
				{
					RemoteRemove(k, singular);
				}
			}
			catch (Exception e)
			{
				continuation(e, null);
			}
		}

		/// <summary>
		///   Performs a mulitple get against the remote caching service. The base implementation
		///   performs a series of singular gets and accumulates the results.
		/// </summary>
		/// <param name="keys">the items' keys</param>
		/// <param name="continuation">a continuation called upon completion</param>
		protected void RemoteGet(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TRep>>> continuation)
		{
			Contract.Requires<ArgumentNullException>(keys != null);

			var accum = new ConcurrentBag<Tuple<TKey, TRep>>();
			var ex = default(Exception);
			var kk = keys as TKey[] ?? keys.ToArray();
			var callbacks = kk.Count();
			Continuation<Tuple<TKey, TRep>> singular = (e, res) =>
			{
				var final = Interlocked.Decrement(ref callbacks) == 0;
				if (Util.VolatileRead(ref ex) == null)
				{
					if (e != null)
					{
						Interlocked.CompareExchange(ref ex, e, null);
					}
					else if (res != null)
					{
						accum.Add(res);
					}
				}
				if (final)
				{
					if (Util.VolatileRead(ref ex) != null)
					{
						continuation(ex, null);
					}
					else
					{
						continuation(null, accum);
					}
				}
			};
			try
			{
				foreach (var k in kk)
				{
					RemoteGet(k, singular);
				}
			}
			catch (Exception e)
			{
				continuation(e, null);
			}
		}

		#region ICache<K,V> Members

		/// <summary>
		///   Gets the cache's name.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		///   Asynchronously gets an item from the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Get(TKey key, Continuation<Tuple<TKey, TValue>> continuation)
		{
			Go.Parallel(() => RemoteGet(key, (e, res) =>
			{
				if (e != null)
				{
					continuation(e, null);
				}
				else if (res == null)
				{
					continuation(null, null);
				}
				else
				{
					continuation(null, Tuple.Create(res.Item1, RestoreItem(res.Item2)));
				}
			}));
		}

		/// <summary>
		///   Asynchronously gets items from the cache.
		/// </summary>
		/// <param name="keys">keys of items to retrieve.</param>
		/// <param name="continuation">a continuation called upon completion</param>
		public void Get(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() => RemoteGet(keys, (e, res) =>
			{
				if (e != null)
				{
					continuation(e, null);
				}
				else if (res == null)
				{
					continuation(null, null);
				}
				else
				{
					var accum = new ConcurrentBag<Tuple<TKey, TValue>>();
					Parallel.ForEach(res, it => accum.Add(Tuple.Create(it.Item1, RestoreItem(it.Item2))));
					continuation(null, accum);
				}
			}));
		}

		/// <summary>
		///   Asynchronously puts an item in the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="item">the item.</param>
		/// <param name="continuation">an optioanl continuation called upon completion</param>
		public void Put(TKey key, TValue item, Continuation<Tuple<TKey, TValue>> continuation)
		{
			Go.Parallel(() => RemotePut(key, TransformItem(item), e =>
			{
				if (e != null)
				{
					continuation(e, null);
				}
				else
				{
					continuation(null, Tuple.Create(key, item));
				}
			}));
		}

		/// <summary>
		///   Asynchronously puts items in the cache.
		/// </summary>
		/// <param name="items">items to put into the cache.</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		public void Put(IEnumerable<Tuple<TKey, TValue>> items, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() =>
			{
				var transformed = new ConcurrentBag<Tuple<TKey, TRep>>();
				var ii = items as Tuple<TKey, TValue>[] ?? items.ToArray();
				try
				{
					Parallel.ForEach(ii, it => transformed.Add(Tuple.Create(it.Item1, TransformItem(it.Item2))));
				}
				catch (Exception e)
				{
					continuation(e, null);
				}
				RemotePut(transformed, e =>
				{
					if (e != null)
					{
						continuation(e, null);
					}
					else
					{
						continuation(null, ii);
					}
				});
			});
		}

		/// <summary>
		///   Asynchronously removes an item from the cache. Only items actually present are returned to the
		///   continuation.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		public void Remove(TKey key, Continuation<Tuple<TKey, TValue>> continuation)
		{
			Go.Parallel(() => RemoteRemove(key, (e, res) =>
			{
				if (e != null)
				{
					continuation(e, null);
				}
				else if (res == null)
				{
					continuation(null, null);
				}
				else
				{
					continuation(null, Tuple.Create(res.Item1, RestoreItem(res.Item2)));
				}
			}));
		}

		/// <summary>
		///   Asynchronously removes items from the cache. Only items actually present are returned to the
		///   continuation.
		/// </summary>
		/// <param name="keys">keys of items to remove.</param>
		/// <param name="continuation">an optional continuation called upon completion</param>
		public void Remove(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<TKey, TValue>>> continuation)
		{
			Go.Parallel(() => RemoteRemove(keys, (e, res) =>
			{
				if (e != null)
				{
					continuation(e, null);
				}
				else if (res == null)
				{
					continuation(null, null);
				}
				else
				{
					var accum = new ConcurrentBag<Tuple<TKey, TValue>>();
					Parallel.ForEach(res, it => accum.Add(Tuple.Create(it.Item1, RestoreItem(it.Item2))));
					continuation(null, accum);
				}
			}));
		}

		/// <summary>
		///   Flushes all items from the cache.
		/// </summary>
		public virtual void FlushAll()
		{
			throw new NotImplementedException();
		}

		#endregion
	}
}